トップレベル定義の導入とパッケージオブジェクトの廃止について:Scala 3のパワーアップした表現力(3)

Scala 3.3.4
最終更新:2020年9月20日

[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください

この記事では、Scala 3で導入される「トップレベル定義」と、廃止予定のパッケージオブジェクトについて解説します。

変数や型エイリアスなどもトップレベルに定義できるようになる

Scala 3では、「トップレベル定義」が強化されました。
これまではパッケージやインポート、クラス、オブジェクトなどが定義できましたが、さらに変数、メソッド、型エイリアスなども定義できるようになりました。

以下のように、トップレベル定義を複数のソースファイルに分散することができます。
つまり、どのファイルにおいてもトップレベルに定義を記述することができるということです。

// first.scala package com.scalapedia type Tag[T] = (String, T) val a: Tag[Int] = ("version", 3) def value = a._2
// second.scala package com.scalapedia case class C() implicit object Cops { extension (x: C) def pair(y: C) = (x, y) }

トップレベルには以下のような定義を記述することができます。

  • すべてのパターン、値、メソッド、および型の定義
  • 暗黙のクラスやオブジェクト
  • 不透明型のエイリアスのコンパニオンオブジェクト

トップレベルに記述されたオブジェクト等は、当該パッケージに直接置かれているものとして扱われます。

アクセス修飾子も使える

privateなどのアクセス修飾子も、通常と同様に使用できます。

つまり、例えばprivateなオブジェクトは当該パッケージ内でのみアクセス可能、といった形です。

同名の定義は同じファイルに記述する

また、複数のトップレベル定義を同じ名前にしてオーバーロードしようとしている場合は、すべて同じファイルに記述してください。

例えば上述の例のようにfirst.scalaに既にdef valueが定義されている場合には、second.scalaに引数の数を変えた別のdef valueを定義することはできないということです。

また、second.scalaに既にcase class Cが定義されている場合には、first.scalaに別途object Cを定義することもできません。

パッケージオブジェクトは歴史的使命を終えて廃止される

Scala 2.8より導入されたパッケージオブジェクトは、一つのパッケージにつき一つのパッケージオブジェクトを作成すると、この中にあらゆる種類の定義を記述できるという機能でした。

Scala 3より、複数のファイルに様々な種類の定義をトップレベルで記述できるようになったため、パッケージオブジェクトはもはや必要なくなりました。

つまり、パッケージオブジェクトは歴史的使命を終えたということです。

削除される具体的な時期は未定

パッケージオブジェクトはこの先廃止されることが決まっています。

Scala 3.0の段階ではまだ利用可能ですが、3系のいずれかのタイミングで非推奨とされ、最終的には削除される予定です。

すぐ移行しなければならないわけではありませんが、今後の動向には留意しておきましょう。

トップレベル定義の実体

トップレベル定義によって、これらの定義をラップするオブジェクトが生成されます。

com.scalapediaパッケージ内のSample.scalaというファイルにトップレベル定義を記述すると、Sample$packageというオブジェクトが生成され、この中に値などが置かれます。

このオブジェクトに置かれたメンバには透過的にアクセスできることになっています。
つまり、これらの定義はcom.scalapediaに置かれているメンバとして扱うことができるというわけです。

ファイル名がバイナリ互換性に影響する可能性がある

このことは、バイナリ互換性に関して注意する必要があることを意味します。

トップレベル定義を含むソースファイルの名前を変更すると、生成されるオブジェクトとそのクラスの名前も変更されます。

つまりバイナリ互換性が崩れるのです。

特にライブラリを提供している方は注意しましょう。

mainメソッドをトップレベルに配置するのは勧められない

以下のようなmainメソッドをトップレベルに宣言することも「技術的には」可能です。

def main(args: Array[String]): Unit = ...

ただし、呼び出し方がややこしくなってしまいます。

mainメソッドをMain.scalaのトップレベルに定義した場合、例えばコマンドラインからは scala Main$package といった形で呼び出すことになります。

つまり呼び出し対象の名前が、トップレベル定義の実体を知っている人にしかわからない珍妙な名前になってしまうので、おすすめしません。

mainメソッドは明示的に命名されたオブジェクトに定義しましょう。

まだ移行しなくてOK

パッケージオブジェクトは将来的に廃止される予定ですが、当面は使用を続けることができます。

新しく定義する場合はトップレベルに定義しましょう。

外部リンク:Dropped: Package Objects

サイト内検索